package group.rober.runtime.autoconfigure;

import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.alibaba.fastjson.serializer.*;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4;
import group.rober.runtime.kit.ArrayKit;
import group.rober.runtime.kit.ListKit;
import group.rober.runtime.kit.StringKit;
import group.rober.runtime.support.BeanPostProcessorImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.*;

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@EnableConfigurationProperties(RuntimeProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@ComponentScan(basePackages = "group.rober.runtime")
public class RuntimeAutoConfiguration extends WebMvcConfigurerAdapter {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private RuntimeProperties properties;

    public RuntimeAutoConfiguration(RuntimeProperties properties) {
        this.properties = properties;
    }


    @Bean("runtimePostProcessor")
    public BeanPostProcessorImpl getBeanPostProcessorImpl(){
        return new BeanPostProcessorImpl();
    }

    /**
     * Druid的StatViewServlet
     * @return
     */
    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean reg = new ServletRegistrationBean();
        reg.setServlet(new StatViewServlet());
        reg.addUrlMappings("/druid/*");
        reg.addInitParameter("loginUsername", properties.getDruidLoginUsername());
        reg.addInitParameter("loginPassword", properties.getDruidLoginPassword());
        return reg;
    }

    /**
     * Druid的StatFilter
     * @return
     */
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        filterRegistrationBean.addInitParameter("profileEnable", "true");
        filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
        filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION");
        return filterRegistrationBean;
    }
    public void addCorsMappings(CorsRegistry registry) {
        if(properties.isCorsEnable()){
            registry.addMapping(properties.getPathPattern())
                    .allowedHeaders(properties.getAllowedHeaders())
                    .allowedMethods(properties.getAllowedMethods())
                    .allowedOrigins(properties.getAllowedOrigins());
        }
    }
//    @Bean
//    public CorsFilter corsFilter() {
//        CorsConfiguration corsConfiguration = new CorsConfiguration();
//        corsConfiguration.addAllowedOrigin("*"); // 1
//        corsConfiguration.addAllowedHeader("*"); // 2
//        corsConfiguration.addAllowedMethod("*"); // 3
//        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
//        source.registerCorsConfiguration("/**", corsConfiguration); // 4
//        return new CorsFilter(source);
//    }



    public StringHttpMessageConverter getStringHttpMessageConverter(){
        StringHttpMessageConverter shmc = new StringHttpMessageConverter(properties.getCharset());
        shmc.setSupportedMediaTypes(ListKit.listOf(new MediaType(MediaType.TEXT_PLAIN,properties.getCharset())));
        return shmc;
    }

    public HttpMessageConverter<Object> getHttpMessageConverter(){
        FastJsonHttpMessageConverter4 jsonHttpMessageConverter = new FastJsonHttpMessageConverter4();

        jsonHttpMessageConverter.setSupportedMediaTypes(ListKit.listOf(
//                new MediaType(MediaType.TEXT_HTML,properties.getCharset()) //避免IE出现下载JSON文件的情况
                new MediaType(MediaType.APPLICATION_JSON,properties.getCharset())
        ));
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(
                SerializerFeature.QuoteFieldNames           //输出key时是否使用双引号
                ,SerializerFeature.WriteMapNullValue        //是否输出值为null的字段
                ,SerializerFeature.WriteNullListAsEmpty     //List字段如果为null,输出为[],而非null
//                ,SerializerFeature.WriteNullNumberAsZero    //数值字段如果为null,输出为0,而非null
                ,SerializerFeature.WriteNullStringAsEmpty   //字符类型字段如果为null,输出为"",而非null
//                ,SerializerFeature.WriteNullBooleanAsFalse  //Boolean字段如果为null,输出为false,而非null
                ,SerializerFeature.WriteDateUseDateFormat   //Date的日期转换器
                ,SerializerFeature.BrowserCompatible        //将Long类型写为String类型解决js精度丢失问题
//                ,SerializerFeature.PrettyFormat             //美化JSON输出
        );
        fastJsonConfig.setDateFormat(properties.getLongDateFormat());
        initClassPropertySecretValueFilter(fastJsonConfig); //JSON需要保密属性，使用过滤器处理
        initClassPropertyExcludesFilter(fastJsonConfig);    //JSON需要去除的属性，使用过滤器处理
        jsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
        jsonHttpMessageConverter.setDefaultCharset(properties.getCharset());

        return jsonHttpMessageConverter;
    }

    private Map<Class<?>,List<String>> propertyExprToMap(List<String> exprList){
        //1.组织类+N个属性的集合
        Map<Class<?>,List<String>> cpMap = new HashMap<Class<?>,List<String>>();
        for(String expr : exprList){
            int lastDotIdx = expr.lastIndexOf(".");
            if(lastDotIdx<=0)continue;
            String className = expr.substring(0,lastDotIdx);
            String propertyName = expr.substring(lastDotIdx+1);
            if(StringKit.isBlank(className)||StringKit.isBlank(propertyName)){
                continue;
            }
            try {
                Class<?> clazz = Class.forName(className);
                if(!cpMap.containsKey(clazz)){
                    cpMap.put(clazz,new ArrayList<String>());
                }
                List<String> pList = cpMap.get(clazz);
                pList.add(propertyName);
            } catch (ClassNotFoundException e) {
                logger.warn("",e);
            }
        }
        return cpMap;
    }

    private void initClassPropertySecretValueFilter(FastJsonConfig fastJsonConfig){
        //1.组织类+N个属性的集合
        Map<Class<?>,List<String>> cpMap = propertyExprToMap(properties.getJsonSerializePropertySecrets());
        //生成过滤器，一个类一个过滤器
        SerializeFilter[] filters = new SerializeFilter[cpMap.size()];
        int i = 0;
        for(Map.Entry<Class<?>,List<String>> entry : cpMap.entrySet()){
            ClassPropertySecretValueFilter filter = new ClassPropertySecretValueFilter(entry.getKey());
            filter.getProperties().addAll(entry.getValue());
            filter.setSecretMask(properties.getJsonSerializeSecretMask());
            filters[i++] = filter;
        }
        SerializeFilter[] sfs = ArrayKit.concat(filters,fastJsonConfig.getSerializeFilters());
        fastJsonConfig.setSerializeFilters(sfs);
    }
    private void initClassPropertyExcludesFilter(FastJsonConfig fastJsonConfig){
        Map<Class<?>,List<String>> cpMap = propertyExprToMap(properties.getJsonSerializePropertyExcludes());
        //生成过滤器，一个类一个过滤器
        SerializeFilter[] filters = new SerializeFilter[cpMap.size()];
        int i = 0;
        for(Map.Entry<Class<?>,List<String>> entry : cpMap.entrySet()){
            ClassPropertyExcludesFilter filter = new ClassPropertyExcludesFilter(entry.getKey());
            filter.getProperties().addAll(entry.getValue());
            filters[i++] = filter;
        }
        SerializeFilter[] sfs = ArrayKit.concat(filters,fastJsonConfig.getSerializeFilters());
        fastJsonConfig.setSerializeFilters(sfs);
    }

    //配置HTTP消息转换器
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        super.configureMessageConverters(converters);
        converters.add(getStringHttpMessageConverter());
        converters.add(getHttpMessageConverter());
    }

    /**
     * 敏感信息JSON序列化隐藏
     */
    public static class ClassPropertySecretValueFilter implements ValueFilter{
        private Class<?>    clazz = null;
        private Set<String> properties = new HashSet<String>();
        private Object secretMask = "******";

        public ClassPropertySecretValueFilter(Class<?> clazz) {
            this.clazz = clazz;
        }

        public Set<String> getProperties() {
            return properties;
        }

        public void setProperties(Set<String> properties) {
            this.properties = properties;
        }

        public Object getSecretMask() {
            return secretMask;
        }

        public void setSecretMask(Object secretMask) {
            this.secretMask = secretMask;
        }

        public Object process(Object object, String name, Object value) {
            if(clazz!=null&&clazz.isInstance(object)&&properties.contains(name)){
                return secretMask;
            }
            return value;
        }
    }

    /**
     * 过滤指定类的字段
     */
    public static class ClassPropertyExcludesFilter implements PropertyPreFilter{
        private Class<?>    clazz = null;
        private Set<String> properties = new HashSet<String>();

        public ClassPropertyExcludesFilter(Class<?> clazz) {
            this.clazz = clazz;
        }

        public Set<String> getProperties() {
            return properties;
        }

        public void setProperties(Set<String> properties) {
            this.properties = properties;
        }

        public boolean apply(JSONSerializer serializer, Object object, String name) {
            if(clazz!=null&&clazz.isInstance(object)&&properties.contains(name)){
                return false;
            }
            return true;
        }
    }

}
